import time
import requests
import pandas as pd
import json
import openpyxl
import pathlib as pl
from random import randint


class SpiderObj:
    """
    爬虫对象/Users/x/PycharmProjects/meituan/data/尊宝披萨.txt
    """
    def __init__(self):
        """
        初始化对象
        """
        self.file_path = pl.Path(input('请输入已保存的店铺数据文件路径：\n'))

    def create_dir(self, shop_name: str) -> tuple:
        """
        检查并创建文件夹
        :param shop_name: 店铺名
        :return:
        """
        shop_dir = self.file_path / shop_name
        pic_dir = shop_dir / '图片'
        if not shop_dir.is_dir():  # 如果店铺文件夹不存在，则创建
            shop_dir.mkdir()
        if not pic_dir.is_dir():  # 如果图片文件夹不存在，则创建
            pic_dir.mkdir()
        return shop_dir, pic_dir

    @classmethod
    def get_origin_price(cls, ser: pd.Series) -> float:
        """
        解析skus中的原价origin_price
        :param ser: 数据行
        :return:
        """
        skus = ser['skus'][0]
        origin_price = skus['origin_price']
        return origin_price

    @classmethod
    def parse_data(cls, filename: pl.Path) -> tuple:
        """
        解析获取到的美团店铺数据
        :param filename: 存储数据的文件路径
        :return:
        """
        with open(filename, 'r', encoding='utf-8') as fin:
            data = fin.read()
        data = json.loads(data)
        # 解析数据步骤
        shop_name = data['data']['poi_info']['name']
        data = data['data']['food_spu_tags']
        df = pd.DataFrame()
        for tag in data:
            dfx = pd.DataFrame(tag['spus'])
            dfx['分类'] = tag['name']
            df = pd.concat([df, dfx])
        df = df.loc[df['分类'].map(lambda x: x not in ['折扣', '热销', '推荐'])]
        df['原价'] = df.apply(cls.get_origin_price, axis=1)
        df.reset_index(inplace=True, drop=True)
        return shop_name, df

    @classmethod
    def download_picture(cls, url: str, filename: pl.Path):
        """
        下载图片的方法
        :param url: 图片的地址
        :param filename: 输出图片的路径(含文件名)
        :return:
        """
        # 初始化请求头
        headers = {
            "accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9",
            "referer": "https://h5.waimai.meituan.com/",
            "sec-ch-ua": "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"97\", \"Chromium\";v=\"97\"",
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": "\"Windows\"",
            "sec-fetch-dest": "image",
            "sec-fetch-mode": "no-cors",
            "sec-fetch-site": "cross-site",
            "user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, "
                          "like Gecko) Mobile/15E217 MicroMessenger/6.8.0(0x16080000) NetType/WIFI Language/en "
                          "Branch/Br_trunk MiniProgramEnv/Mac "
        }
        # 下载文件
        file = requests.get(url, headers, stream=True)
        with open(filename, "wb") as code:
            for chunk in file.iter_content(chunk_size=1024):  # 边下载边存硬盘
                if chunk:
                    code.write(chunk)

    @classmethod
    def get_pictures(cls, shop_name: str, data: pd.DataFrame, pic_dir: pl.Path):
        """
        批量获取图片数据的方法
        :param shop_name: 店铺名
        :param data: 数据
        :param pic_dir: 图片存放的目录
        :return:
        """
        print(f'开始下载店铺：{shop_name} 的图片')
        # 下载前按照菜品名称与图片地址进行去重处理，减少请求数量
        download_data = data.copy()
        download_data = download_data.drop_duplicates(
            ['name', 'picture'], keep='last')
        # 筛选去除图片地址为空的
        download_data = download_data.loc[
            (download_data['picture'].map(lambda x: pd.notnull(x))) |
            (download_data['picture'] != '')
            ]
        max_len = len(download_data)
        for idx, food in enumerate(download_data.to_dict(orient='records')):  # 遍历数据
            pic_url = food['picture']
            # 拆分获取图片扩展名
            suffix = pl.Path(pic_url.split('/')[-1]).suffix
            # 加工出图片的路径(包含名称)
            name = food['name'].replace('\\', '_').replace('/', '_')
            filename = pic_dir / f"{name}{suffix}"
            # 使用下载方法下载
            try:
                cls.download_picture(pic_url, filename)
                print(f'({idx + 1}/{max_len})菜品:{food["name"]} 图片下载完成')
            except Exception as e:
                print(
                    f'!!!({idx + 1}/{max_len})菜品:{food["name"]} 图片下载失败，错误提示是： {e}')
            # 随机暂停
            time.sleep(randint(1, 3) / 10)

    @classmethod
    def write_data(cls, shop_name, data, shop_dir):
        """
        将数据输出至excel文件
        :param shop_name: 店铺名
        :param data: 数据
        :param shop_dir: 店铺存放的文件夹
        :return:
        """
        data.to_excel(shop_dir / f'{shop_name}.xlsx', index=False)

    def run(self):
        """
        运行程序
        :return:
        """
        try:
            for filename in self.file_path.iterdir():
                # 先解析人工取得的数据
                shop_name, data = self.parse_data(filename)
                # 再创建文件夹
                shop_dir, pic_dir = self.create_dir(shop_name)
                # 写入Excel文件
                self.write_data(shop_name, data, shop_dir)
                # 获取图片
                self.get_pictures(shop_name, data, pic_dir)
                print(
                    f'店铺：{shop_name}的数据已解析下载完毕，数据存储在：“{shop_dir.absolute()}”路径下')
            return True, None
        except Exception as e:
            return False, e


if __name__ == '__main__':
    spider = SpiderObj()
    res, err = spider.run()
    if res:
        input('程序已运行完毕，按回车键退出')
    else:
        input(f'程序运行出错，错误提示是： {err}')
